<?php

/**
 * Created by : PhpStorm
 * @date: 2024/6/10 10:17
 */

namespace vendor\skj\webman_class_library\ClassLibrary;

use ClassLibrary\ClCache;
use ClassLibrary\ClFile;
use ClassLibrary\ClString;
use ClassLibrary\ClXml;

/**
 * curl请求
 * Class ClRequest
 * @package vendor\skj\webman_class_library\ClassLibrary
 * @date 2024/6/10 10:17
 */
class ClCurl {
    /**
     * 请求结果
     * @var string
     * @date 2024/6/10 10:19
     */
    private $result_type = 'JSON';

    /**
     * url
     * @var string
     * @date 2024/6/10 10:20
     */
    private $url = '';

    /**
     * 参数
     * @var array
     * @date 2024/6/10 10:26
     */
    private $params = [];

    /**
     * 缓存时间
     * @var int
     * @date 2024/6/10 10:27
     */
    private $duration = 0;

    /**
     * 是否调试
     * @var int
     * @date 2024/6/10 10:30
     */
    private $debug = 0;

    /**
     * Header
     * @var array
     * @date 2024/6/10 10:31
     */
    private $header = [];

    /**
     * 设置来源
     * @var string
     * @date 2024/6/10 10:32
     */
    private $refer = '';

    /**
     * CURLOPT_CUSTOMREQUEST值，GET,POST,PUT,DELETE
     * @var string
     * @date 2024/6/10 10:34
     */
    private $custom_request = '';

    /**
     * 超时时间
     * @var int
     * @date 2024/6/10 10:37
     */
    private $timeout = 30;

    /**
     * 代理ip
     * @var string
     * @date 2024/6/10 10:39
     */
    private $proxy_ip = '';

    /**
     * 代理端口
     * @var string
     * @date 2024/6/10 10:39
     */
    private $proxy_port = '';

    /**
     * 代理密码
     * @var string
     * @date 2024/6/10 10:39
     */
    private $proxy_password = '';

    /**
     * 浏览器信息
     * @var string
     * @date 2024/6/10 12:03
     */
    private $useragent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36 Core/1.47.640.400 QQBrowser/9.4.8309.400';

    /**
     * cookie
     * @var string
     * @date 2024/6/10 17:20
     */
    private $cookie = '';

    /**
     * 获取cookie的字段
     * @var null
     * @date 2024/6/10 22:06
     */
    private $get_cookie_field = null;

    /**
     * 获取header字段，此变量是引用传递，不可进行重置动作
     * @var null
     * @date 2024/6/10 22:06
     */
    private $get_header_field = null;

    /**
     * 下载文件绝对地址
     * @var string
     * @date 2025/6/12 10:59
     */
    private $down_file_path = '';

    /**
     * 实例对象
     * @var array
     * @author SongKeJing qq:597481334 mobile:159-5107-8050
     * @date 2022/4/17 21:59
     */
    private static $ins = [];

    /**
     * 实例化
     * @param string $url 请求地址
     * @return static
     * @author SongKeJing qq:597481334 mobile:159-5107-8050
     * @date 2022/4/17 22:02
     */
    public static function instance(string $url) {
        // 依据当前类解决冲突问题
        $static_class_name = (static::class) . $url;
        if (!isset(self::$ins[$static_class_name])) {
            self::$ins[$static_class_name] = new static();
        }
        self::$ins[$static_class_name]->url = $url;
        return self::$ins[$static_class_name];
    }

    /**
     * 重置
     * @date 2024/6/10 11:30
     */
    private function reset() {
        $this->result_type    = 'JSON';
        $this->url            = '';
        $this->params         = [];
        $this->duration       = 0;
        $this->debug          = 0;
        $this->header         = [];
        $this->refer          = '';
        $this->custom_request = '';
        $this->timeout        = 30;
        $this->proxy_ip       = '';
        $this->proxy_port     = '';
        $this->proxy_password = '';
        $this->useragent      = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36 Core/1.47.640.400 QQBrowser/9.4.8309.400';
        $this->cookie         = '';
        // 采用unset，进行取消引用传递
        unset($this->get_cookie_field);
        // 再次赋值
        $this->get_cookie_field = null;
        // 采用unset，进行取消引用传递
        unset($this->get_header_field);
        // 再次赋值
        $this->get_header_field = null;
        // 下载文件
        $this->down_file_path   = '';
    }

    /**
     * 数组参数
     * @param array $params
     * @return $this
     * @date 2024/6/10 19:59
     */
    public function params(array $params) {
        $this->params = $params;
        return $this;
    }

    /**
     * 缓存时间
     * @param int $duration
     * @return $this
     * @date 2024/6/10 20:13
     */
    public function duration(int $duration) {
        $this->duration = $duration;
        return $this;
    }

    /**
     * 返回JSON格式
     * @return $this
     * @date 2024/6/10 12:18
     */
    public function resultTypeJson() {
        $this->result_type = 'JSON';
        return $this;
    }

    /**
     * 返回XML格式
     * @return $this
     * @date 2024/6/10 12:18
     */
    public function resultTypeXml() {
        $this->result_type = 'XML';
        return $this;
    }

    /**
     * 默认空，不进行处理
     * @return $this
     * @date 2024/6/10 12:18
     */
    public function resultTypeDefault() {
        $this->result_type = '';
        return $this;
    }

    /**
     * 调试
     * @return $this
     * @date 2024/6/11 10:57
     */
    public function debugYes() {
        $this->debug = 1;
        return $this;
    }

    /**
     * 非调试
     * @return $this
     * @date 2024/6/10 12:19
     */
    public function debugNo() {
        $this->debug = 0;
        return $this;
    }

    /**
     * 设置Header，["Authorization:628906641320240605145515838","Content-Type: application/json;charset=utf8"]
     * @param array $header
     * @return $this
     * @date 2024/6/10 10:31
     */
    public function header(array $header) {
        $this->header = array_merge($this->header, $header);
        return $this;
    }

    /**
     * 设置来源
     * @param string $refer
     * @return $this
     * @date 2024/6/10 10:33
     */
    public function refer(string $refer) {
        $this->refer = $refer;
        return $this;
    }

    /**
     * GET
     * @return $this
     * @date 2024/6/10 12:05
     */
    public function customRequestGet() {
        $this->custom_request = 'GET';
        return $this;
    }

    /**
     * POST
     * @return $this
     * @date 2024/6/10 12:16
     */
    public function customRequestPost() {
        $this->custom_request = 'POST';
        $this->header[]       = 'X-HTTP-Method-Override: POST';
        return $this;
    }

    /**
     * PUT
     * @return $this
     * @date 2024/6/10 12:17
     */
    public function customRequestPut() {
        $this->custom_request = 'PUT';
        $this->header[]       = 'X-HTTP-Method-Override: PUT';
        return $this;
    }

    /**
     * DELETE
     * @return $this
     * @date 2024/6/10 12:17
     */
    public function customRequestDelete() {
        $this->custom_request = 'DELETE';
        $this->header[]       = 'X-HTTP-Method-Override: DELETE';
        return $this;
    }

    /**
     * 设置超时时间
     * @param int $timeout
     * @return $this
     * @date 2024/6/10 10:37
     */
    public function timeout(int $timeout = 30) {
        $this->timeout = $timeout;
        return $this;
    }

    /**
     * 设置代理ip
     * @param string $proxy_ip
     * @return $this
     * @date 2024/6/10 10:40
     */
    public function proxyIp(string $proxy_ip) {
        $this->proxy_ip = $proxy_ip;
        return $this;
    }

    /**
     * 设置代理端口
     * @param string $port
     * @return $this
     * @date 2024/6/10 10:41
     */
    public function proxyPort(string $port) {
        $this->proxy_port = $port;
        return $this;
    }

    /**
     * 设置代理密码
     * @param string $password
     * @return $this
     * @date 2024/6/10 10:42
     */
    public function proxyPassword(string $password) {
        $this->proxy_password = $password;
        return $this;
    }

    /**
     * 浏览器信息
     * @param string $useragent
     * @return $this
     * @date 2024/6/10 12:04
     */
    public function useragent(string $useragent) {
        $this->useragent = $useragent;
        return $this;
    }

    /**
     * 设置下载的文件
     * @param string $file_path 文件绝对地址
     * @return $this
     * @date 2025/6/12 11:11
     */
    public function download(string $file_path) {
        log_info('$this->down_file_path0:', $file_path);
        $this->down_file_path = $file_path;
        return $this;
    }

    /**
     * cookie内容
     * @param string $cookie
     * @return $this
     * @date 2024/6/10 17:21
     */
    public function cookie(string $cookie) {
        $this->cookie = $cookie;
        return $this;
    }

    /**
     * 获取cookie变量
     * @param string $cookie_field
     * @return $this
     * @date 2024/6/10 17:34
     * @throws \Exception
     */
    public function getCookie(&$cookie_field) {
        if ($cookie_field === null) {
            throw new \Exception('变量cookie_field未定义');
        }
        $this->get_cookie_field = &$cookie_field;
        return $this;
    }

    /**
     * 获取返回header内容
     * @param string $response_header_field
     * @return $this
     * @date 2024/6/10 17:36
     * @throws \Exception
     */
    public function getResponseHeader(&$response_header_field) {
        if ($response_header_field === null) {
            throw new \Exception('变量response_header_field未定义');
        }
        $this->get_header_field = &$response_header_field;
        return $this;
    }

    /**
     * 发送请求
     * @return bool|mixed|string
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \throwable
     * @date 2024/6/10 12:25
     */
    public function send() {
        if ($this->duration > 0) {
            $key    = ClCache::getKey($this->url, $this->params);
            $result = cache($key);
            if ($result !== false) {
                // 解析HTTP数据流
                list($header, $result) = explode("\r\n\r\n", $result);
                if ($this->get_header_field !== null) {
                    $this->get_header_field = $header;
                }
                if ($this->result_type == 'JSON') {
                    $result = json_decode($result, true);
                } else if ($this->result_type == 'XML') {
                    $result = ClXml::toArray($result);
                }
                // 重置
                $this->reset();
                return $result;
            }
        }

        $ch                 = curl_init();
        // 是否启用长连接+HTTP心跳
        $is_long_connection = false;
        // 用于发送心跳的流
        $stream             = null;

        // 处理包含文件的参数
        $is_post_file = false;
        if (is_array($this->params)) {
            foreach ($this->params as $param_index => $param_value) {
                if (!is_string($param_value)) {
                    continue;
                }
                if (strpos($param_value, '@') === 0) {
                    $is_post_file = true;
                    $param_value  = substr($param_value, 1);
                    // 如果存在文件
                    clearstatcache();
                    if (is_file($param_value)) {
                        // 兼容5.0-5.6版本的curl
                        if (class_exists('\CURLFile')) {
                            $this->params[$param_index] = new \CURLFile(realpath($param_value), mime_content_type($param_value), ClFile::getName($param_value, true));
                        } else {
                            if (defined('CURLOPT_SAFE_UPLOAD')) {
                                curl_setopt($ch, CURLOPT_SAFE_UPLOAD, FALSE);
                            }
                        }
                        // 文件的上传，Content-Type:multipart/form-data
                        $is_match = false;
                        foreach ($this->header as $index_header => $each_header) {
                            if (strpos(strtolower($each_header), 'content-type') === 0) {
                                $is_match                    = true;
                                // 覆盖
                                $this->header[$index_header] = 'Content-Type: multipart/form-data';
                            }
                        }
                        if (!$is_match) {
                            // 新增
                            $this->header[] = 'Content-Type: multipart/form-data';
                        }
                    }
                }
            }
        }

        if (strpos($this->url, 'https') === 0) {
            // 跳过证书检查
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            // 从证书中检查SSL加密算法是否存在
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }

        curl_setopt($ch, CURLOPT_URL, $this->url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

        if (!empty($this->header)) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $this->header);
        }

        // 超时时间设置（核心修改：HTTP层心跳）
        if ($this->timeout > 0) {
            curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
            // 当超时>5分钟，启用HTTP层心跳（替代TCP保活）
            if ($this->timeout > 300) {
                $is_long_connection = true;
                // 1. 启用分块传输编码（允许动态发送数据）
                $this->header[]     = 'Transfer-Encoding: chunked';
                $this->header[]     = 'Connection: keep-alive';
                curl_setopt($ch, CURLOPT_HTTPHEADER, $this->header);
                // 2. 创建临时流（用于写入心跳数据）
                $stream = fopen('php://temp', 'r+');
                curl_setopt($ch, CURLOPT_INFILE, $stream);
                curl_setopt($ch, CURLOPT_INFILESIZE, -1);  // 分块传输不指定固定大小
            }
        }
        // 处理POST参数（如果是长连接，需确保参数先发送）
        if (!empty($this->params)) {
            // json格式特殊处理
            if (!empty($this->header) && is_array($this->params)) {
                foreach ($this->header as $each) {
                    if (strpos($each, 'application/json') !== false) {
                        $this->params = json_encode($this->params, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
                        break;
                    }
                }
            }
            curl_setopt($ch, CURLOPT_POST, 1);
            // 文件上传不可build
            curl_setopt($ch, CURLOPT_POSTFIELDS, $is_post_file ? $this->params : (is_array($this->params) ? http_build_query($this->params) : $this->params));
        }
        // 来源
        if (!empty($this->refer)) {
            curl_setopt($ch, CURLOPT_REFERER, $this->refer);
        }
        // 请求方式
        if (in_array($this->custom_request, ['GET', 'POST', 'PUT', 'DELETE'])) {
            if ($this->custom_request == 'GET') {
                // 设置为 GET 请求（默认就是 GET，可省略）
                curl_setopt($ch, CURLOPT_HTTPGET, true);
                // get请求参数处理
                if (!empty($this->params)) {
                    if (strpos($this->url, '?') === false) {
                        $this->url .= '?' . http_build_query($this->params);
                    } else {
                        $this->url .= '&' . http_build_query($this->params);
                    }
                    // 重新覆盖url
                    curl_setopt($ch, CURLOPT_URL, $this->url);
                }
            } else {
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->custom_request);
            }
        }
        // cookie内容
        if (!empty($this->cookie)) {
            curl_setopt($ch, CURLOPT_COOKIE, $this->cookie);
        }
        // 设置代理
        if (!empty($this->proxy_ip) && !empty($this->proxy_port)) {
            curl_setopt($ch, CURLOPT_PROXY, $this->proxy_ip);
            curl_setopt($ch, CURLOPT_PROXYPORT, $this->proxy_port);
            if (!empty($this->proxy_password)) {
                curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->proxy_password);
            }
        }
        // 浏览器
        curl_setopt($ch, CURLOPT_USERAGENT, $this->useragent);
        curl_setopt($ch, CURLOPT_HEADER, true);

        // 文件句柄
        $fp = null;
        if ($this->debug) {
            log_info('url:', $this->url);
            if (!empty($this->header)) {
                log_info('header:', json_encode($this->header, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
            }
            if (!empty($this->params)) {
                log_info('params:', is_array($this->params) ? json_encode($this->params, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $this->params);
            }
            if (!empty($this->cookie)) {
                log_info('cookie:', $this->cookie);
            }
        }

        // 执行请求（长连接需用multi_handle实现非阻塞+心跳）
        $content = '';
        if ($is_long_connection && $stream) {
            // 使用multi_handle实现非阻塞执行，同时发送心跳
            $multiHandle = curl_multi_init();
            curl_multi_add_handle($multiHandle, $ch);

            $running           = null;
            // 定时发送一次HTTP心跳（小于Nginx超时）
            $heartbeatInterval = 30;
            $lastHeartbeatTime = time();

            do {
                // 非阻塞执行curl
                curl_multi_exec($multiHandle, $running);
                // 阻塞1秒等待活动
                curl_multi_select($multiHandle, 1);

                // 检查是否需要发送心跳
                $currentTime = time();
                if ($currentTime - $lastHeartbeatTime >= $heartbeatInterval) {
                    // 发送HTTP分块心跳（格式：16进制长度\r\n数据\r\n）
                    // 发送空块（长度0），多数服务器会忽略但刷新超时计时器
                    fwrite($stream, "0\r\n\r\n");
                    // 强制刷新流，确保数据发送
                    fflush($stream);
                    $lastHeartbeatTime = $currentTime;
                    if ($this->debug) {
                        log_info('发送HTTP心跳成功', date('Y-m-d H:i:s'));
                    }
                }
            } while ($running > 0);
            // 获取结果
            $content = curl_multi_getcontent($ch);
            curl_multi_remove_handle($multiHandle, $ch);
            curl_multi_close($multiHandle);
        } else {
            // 普通短连接直接执行
            $content = curl_exec($ch);
        }

        // 错误处理
        if ($error = curl_error($ch)) {
            log_info('Error', curl_error($ch));
            curl_close($ch);
            if ($stream) {
                fclose($stream);
            }
            return [];
        }

        // 缓存处理
        if ($this->duration > 0) {
            $key = ClCache::getKey($this->url, $this->params);
            cache($key, $content, $this->duration);
        }

        // 解析HTTP数据流
        if (empty($content)) {
            $header = $response = '';
        } else {
            list($header, $response) = explode("\r\n\r\n", $content);
            $content_array           = explode("\r\n\r\n", $content);
            if (count($content_array) == 2) {
                list($header, $response) = $content_array;
            } else {
                $response = end($content_array);
                $header   = implode("\r\n\r\n", array_slice($content_array, 0, count($content_array) - 1));
            }
            if (!empty($this->down_file_path)) {
                $file_pathinfo = pathinfo($this->down_file_path);
                if (isset($file_pathinfo['dirname'])) {
                    // 创建目录
                    if (!is_dir($file_pathinfo['dirname'])) {
                        // 递归创建目录
                        mkdir($file_pathinfo['dirname'], 0755, true);
                    }
                    file_put_contents($this->down_file_path, $response);
                }
            }
        }
        // 关闭
        curl_close($ch);
        if ($stream) {
            fclose($stream);
        }
        if (is_resource($fp)) {
            fclose($fp);
        }

        // 调试日志
        if ($this->debug) {
            log_info('response_header:', $header);
            if (empty($this->down_file_path)) {
                log_info('response_body:', $response);
            }
        }

        // 解析Cookie和Header
        if ($this->get_cookie_field !== null) {
            $lines   = explode("\n", $header);
            $cookies = [];
            foreach ($lines as $line) {
                if (strpos(strtolower($line), 'set-cookie:') !== false) {
                    $cookies[] = trim(ClString::getBetween($line, ':', ';', false));
                }
            }
            $this->get_cookie_field = implode('; ', $cookies);
        }
        // header变量
        if ($this->get_header_field !== null) {
            $this->get_header_field = $header;
        }

        // 处理响应格式
        if (!empty($response)) {
            if ($this->result_type == 'JSON') {
                $response = json_decode($response, true);
            } else if ($this->result_type == 'XML') {
                $response = ClXml::toArray($response);
            }
        }
        // 重置
        $this->reset();
        return $response;
    }
}
